This document is a very hasty attempt to give an overview of what scripting in OpenDoc means to a Part Editor developer. The sample code at the end has been revised to reflect changes in OpenDoc for DR4, but the document is otherwise nearly the same as earlier versions.
For additional information, look in the Recipes folder inside the Documentation folder.
First we'll look at how you address a part via AppleScript. Then we'll look at the code your part needs to have to make it happen.
From AppleScript:
The current versions of AppleScript (1.0 or 1.1) don't know anything about OpenDoc. They expect you to address OpenDoc documents as if they were applications. Thus you begin a script
Tell application "My OpenDoc Document"
The terminology you need to talk about parts lives in the OpenDocLib library (where you can't read it with dictionary browsers, and whence GetAETE event handlers retrieve it for the AppleScript compiler on demand.) The only new class you'll need is "part." But you don't even need that to talk to the root part, as it is synonymous with the document where properties and elements of parts are concerned. Thus to get the color of the root part (assuming a root part that supports the color property), you say:
Tell application "My OpenDoc Document"
color
end tell
While parts have the option of supporting access to their embedded parts any way they see fit, three forms of access are supported for *all* parts through the Default Semantic Interface (DSI). Object accessors within the DSI make it possible to script parts that are embedded within non-scriptable parts. (The DSI also provides Get/SetData handlers for a group of properties corresponding to those available through the Part/Frame Info dialog.) Default access is provided by index, by name, and by (document-unique) persistent ID. And you can get both the name (which the user can set) and the ID from a part (via the default GetData handler, of course):
Tell application "My OpenDoc Document"
set theName to name of part 1 -- part 1 is the first embedded part
set theID to ID of part theName -- this is the same part, but gotten by name
tell part ID theID -- ID is a persistent reference the user can't change
set comments to "OpenDoc is here!"
end tell
end tell
Access by both name and ID work anywhere within the specified container. Once you have a valid name or ID, you can use it to refer to a part specifying only the document (application) as its container. The default accessors will return an error, however, if you specify a container that is not valid -- so don't do this unless it makes sense.
tell part ID theID of part ID theID -- IDs are unique, so this is guaranteed to fail
doSomething()
end tell
Because the root part is the same as the document where AppleScript is concerned, there is no way to get it by index ("Part 1" means the first part embedded within the root.) Thus you have to get the ID or name of the root part and use this to refer to it in cases where you need a specific reference to the part:
Tell application "My OpenDoc Document"
set theName to name
tell part theName to doSomething()
set theID to ID
tell part ID theID to doSomething()
-- or simply
tell part name to doSomething() -- ""name" means the name of the document/root part
tell part ID ID to doSomething() -- the second "ID" means the ID of the document/root part
-- simplest of all, of course, since the document *is* the root part for scripting
DoSomething()
end tell
For a more extensive example -- a script making use of lists and testing and setting various properties of parts within a complicated document -- see the script "OpenDoc Demo Script" somewhere near this file on your CD.
All of the above examples, excepting the one asking for "color," work on any document whether its parts are scriptable or not. But for users to be able to do anything interesting or useful via scripting they'll need scriptable parts. Which is what the next section is intended to get you started writing.
Within OpenDoc:
In order to make your part scriptable, you first need to instantiate a subclass of the ODSemanticInterface class. One example of such a class is ODCPlusSemanticInterface. However, your part class must have its own version of this class with a unique name. To create one:
1. Use the PartMaker document "Create Semantic Interface". For more information on PartMaker, see the folder "PartMaker 4.4"
2. Put the generated .cpp file into your project and place the .xh and .xih file in a folder where your compiler can find them. The .idl file that is generated is only included for your information. You won't need to use this file unless you plan to alter the SOM class for your own purposes.
You may have to make some other minor changes to these files, depending on your exception handling scheme and the like.
3. In your part initialization code, include the following calls:
if (!ODISOStrCompare( (ODISOStr)extensionName, kODExtSemanticInterface ))
return _fSemtIntf;
else
return kODNULL;
}
If you've worked with Apple Events before in a "normal" (pre-OpenDoc) application, you're probably familiar with the Object Model and the use of accessors and the Object Support Library to resolve object specifiers. Object Specifiers within the OpenDoc context work much the same way, but with the additional complication that parts (or the DSI acting on their behalf) must cooperate to help the OSL "swap" across part boundaries. In an application, the app itself is the "null" container; in OpenDoc, each part in the containment hierarchy starts as "null" when its turn comes.
Also different in OpenDoc is the order in which object resolution and event dispatch are done. Applications call AEProcessAppleEvent, which dispatches the event to the right handler. Any object specifiers within the event are resolved using the OSL's AEResolve() call. But OpenDoc begins by resolving the direct parameter itself (if present); only after doing so does it know to which part it should dispatch the event.
Consider the object specifier
color of part "fred" of part 1 of part ID 6 of application "my OpenDoc Doc"
Though this can be resolved entirely with DSI object accessors, it's useful to consider the order in which the parts involved get accessor calls if they're scriptable. First is the root part (which corresponds to the application here): it gets asked for part ID 6 from "null". And once it returns a token representing that part it gets asked for part 1 of that token. Since it cannot get anything out of a part it contains (in general, anyway), it "swaps," telling OpenDoc to ask another part (the one for which it has a token, part ID 6) instead.
And so OpenDoc calls that part's accessor for part 1 from null. And so it continues: that part will return a token for part 1, but on being asked for part "fred" will swap so that part 1 gets asked for part "fred." After yet another swap, part "fred" will be asked for a token for the property "color" and object resolution will be over. Only now will the GetData event handler installed by part "fred" be called, the token for property color having replaced the object specifier in its direct parameter.
Here is some sample code drawn from the object accessors and event handlers within the DSI. Consider it for its illustrative value only, since the fact that it's in the DSI means you'll never need to duplicate it inside your own part editor. Also be warned that this code has never been compiled: it's close to what's in our sources but the changes I made moving it here may well have broken something.